#pragma once

#include "PlayerBase.cpp"

class RTMPlayer : public PlayerBase {
public:
    RTMPlayer(std::shared_ptr<char> &&address) : rtmpAddress{std::move(address)} {
        std::thread t{[this] {
            std::lock_guard<std::mutex> lockGuard{stateMutex};
            initContext();
        }};
        t.detach();
    }

    ~RTMPlayer() {
        std::lock_guard<std::mutex> lockGuard{stateMutex};
        pendingState = VideoStateEnum::destroy;
        releaseContext();
    }

    void resumePlay() override {
        if (pendingState != VideoStateEnum::destroy)
            pendingState = VideoStateEnum::resume;
        std::thread t{[this] {
            std::lock_guard<std::mutex> lockGuard{stateMutex};
            if (pendingState != VideoStateEnum::resume ||
                videoState == VideoStateEnum::resuming ||
                videoState == VideoStateEnum::resume) {
                return;
            }
            setState(VideoStateEnum::resuming);
            LOGD("ggg %s resume start",rtmpAddress.get());
            //初始化失败了释放资源
            if (!initContext()) {
                LOGE("ggg %s resume initContext error end",rtmpAddress.get());
                releaseContext();
                return;
            }
            //nativeWindow资源准备
            if (gPSurface != nullptr) {
                JniEnv env;
                while (pNativeWindow == nullptr || vHeight <= 0 || vWidth <= 0) {
                    if (errCount.load() > retryCount) {
                        LOGE("ggg %s resume NativeWindow end",rtmpAddress.get());
                        releaseContext();
                        return;
                    }
                    if (errCount.load() > 0) std::this_thread::sleep_for(64ms);
                    pNativeWindow = ANativeWindow_fromSurface(*env, gPSurface);
                    if (pNativeWindow != nullptr) {
                        vHeight = ANativeWindow_getHeight(pNativeWindow);
                        vWidth = ANativeWindow_getWidth(pNativeWindow);
                    }
                    errCount++;
                }
                errCount.store(0);
            } else {
                LOGE("ggg %s resume Surface end",rtmpAddress.get());
                releaseContext();
                return;
            }
            ANativeWindow_setBuffersGeometry(pNativeWindow, vWidth, vHeight,
                                             WINDOW_FORMAT_RGBA_8888);

            av_read_play(pFormatCtx);
            avio_flush(pFormatCtx->pb);
            avformat_flush(pFormatCtx);

            //清空之前的队列
            qFrameRGBA.clean();
            qPacket.clean();

            //开始循环
            loop.resume();
            setState(VideoStateEnum::resume);
            LOGD("ggg %s resume end",rtmpAddress.get());
        }};
        t.detach();
    }

    void pausePlay() override {
        if (pendingState != VideoStateEnum::destroy && videoState != VideoStateEnum::destroy)
            pendingState = VideoStateEnum::pause;
        std::thread t{[this] {
            std::lock_guard<std::mutex> lockGuard{stateMutex};
            if (pendingState != VideoStateEnum::pause ||
                videoState == VideoStateEnum::destroy ||
                videoState == VideoStateEnum::pausing ||
                videoState == VideoStateEnum::pause) {
                return;
            }
            setState(VideoStateEnum::pausing);
            LOGD("ggg %s pause start",rtmpAddress.get());
            loop.pause();
            av_read_pause(pFormatCtx);
            ANativeWindow_release(pNativeWindow);
            pNativeWindow = nullptr;
            vHeight = -1;
            vWidth = -1;
            setState(VideoStateEnum::pause);
            LOGD("ggg %s pause end",rtmpAddress.get());
        }};
        t.detach();
    }

protected:
    void read() override {
        LOGD("fff %s read start",rtmpAddress.get());
        //尝试读取一个压缩帧
        auto packet{std::make_shared<AVPacket>()};
        if (av_read_frame(pFormatCtx, packet.get()) >= 0 && packet->stream_index == videoStream) {
            errCount.store(0);
            qPacket.push(std::move(packet));
            LOGI("fff %s read succeed",rtmpAddress.get());
        } else if (errCount.load() >= retryCount) {
            LOGE("fff %s read releaseContext",rtmpAddress.get());
            std::lock_guard<std::mutex> lockGuard{stateMutex};
            releaseContext();
        } else {
            errCount++;
            LOGE("fff %s read error %d",rtmpAddress.get(), errCount.load());
        }
    }

    void decode() override {
        LOGD("fff %s decode start",rtmpAddress.get());
        //尝试获取一个压缩帧
        std::shared_ptr<AVPacket> packet;
        try {
            packet = qPacket.tryGetOne();
        } catch (...) {
            LOGE("fff %s decode empty queue",rtmpAddress.get());
            return;
        }
        //尝试解码
        int finished = -1;
        auto frame{getFrame()};
        avcodec_decode_video2(pCodecCtx, frame.get(), &finished, packet.get());
        if (finished == 0) return;
        //格式转换
        auto frameRGBA{getFrame(true)};
        uint8_t *pBuffer = (uint8_t *)
                av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGBA, vWidth, vHeight, 1) *
                          sizeof(uint8_t));
        av_image_fill_arrays(frameRGBA->data, frameRGBA->linesize, pBuffer,
                             AV_PIX_FMT_RGBA, vWidth, vHeight, 1);
        SwsContext *pSws = sws_getContext(pCodecCtx->width, pCodecCtx->height,
                                          pCodecCtx->pix_fmt,
                                          vWidth, vHeight, AV_PIX_FMT_RGBA,
                                          SWS_FAST_BILINEAR, NULL, NULL, NULL);
        sws_scale(pSws, (uint8_t const *const *) frame->data, frame->linesize, 0,
                  pCodecCtx->height, frameRGBA->data, frameRGBA->linesize);
        sws_freeContext(pSws);
        //保存
        qFrameRGBA.push(std::move(frameRGBA));
        LOGD("fff %s decode succeed",rtmpAddress.get());
    }

    void renderer() override {
        LOGD("fff %s rend start",rtmpAddress.get());
        //尝试获取一个解码帧
        std::shared_ptr<AVFrame> frameRGBA;
        try {
            frameRGBA = qFrameRGBA.tryGetOne(true);
        } catch (...) {
            LOGE("fff %s rend empty queue",rtmpAddress.get());
            return;
        }
        if (ANativeWindow_lock(pNativeWindow, &windowBuffer, 0) >= 0) {
            // 获取stride
            uint8_t *dst = (uint8_t *) windowBuffer.bits;
            int dstStride = windowBuffer.stride * 4;
            uint8_t *src = (frameRGBA->data[0]);
            size_t srcStride = (size_t) frameRGBA->linesize[0];
            // 由于window的stride和帧的stride不同,因此需要逐行复制
            for (int h = 0; h < vHeight; h++) {
                memcpy(dst + h * dstStride, src + h * srcStride, srcStride);
            }
            ANativeWindow_unlockAndPost(pNativeWindow);
        }
        LOGD("fff %s rend succeed",rtmpAddress.get());
    }

    void releaseContext() override {
        LOGD("ccc %s release start",rtmpAddress.get());
        loop.reset();
        pool.reset();
        qFrameRGBA.clean();
        qPacket.clean();
        if (pCodecCtx != nullptr){
            avcodec_close(pCodecCtx);
            avcodec_free_context(&pCodecCtx);
        }
        if (pFormatCtx != nullptr) {
            avformat_close_input(&pFormatCtx);
            avformat_free_context(pFormatCtx);
        }
        if (pNativeWindow != nullptr) {
            ANativeWindow_release(pNativeWindow);
        }

        pFormatCtx = nullptr;
        pCodecCtx = nullptr;
        pNativeWindow = nullptr;
        setState(VideoStateEnum::destroy);
        LOGD("ccc %s release end",rtmpAddress.get());
    }

    bool initContext() override {
        if (pCodecCtx != nullptr) return true;//初始化一次
        LOGD("ccc %s initContext start",rtmpAddress.get());
        if (rtmpAddress.get() == nullptr || strlen(rtmpAddress.get()) <= 0) {
            LOGE("ccc %s initContext address end",rtmpAddress.get());
            return false;
        }
        av_register_all();
        pFormatCtx = avformat_alloc_context();//分配Ctx
        if (pFormatCtx == nullptr) {
            LOGE("ccc %s initContext pFormatCtx end",rtmpAddress.get());
            return false;
        }
        int open_input = avformat_open_input(&pFormatCtx, rtmpAddress.get(), nullptr, nullptr);
        if (open_input != 0) {
            LOGE("ccc %s avformat_open_input pFormatCtx end",rtmpAddress.get());
            return false;
        }
        if (avformat_find_stream_info(pFormatCtx, nullptr) < 0) {
            LOGE("ccc %s initContext avformat_find_stream_info end",rtmpAddress.get());
            return false;
        }
        for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
            if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
                videoStream = i;
                break;
            }
        }
        if (videoStream == -1) {
            LOGE("ccc %s initContext videoStream end",rtmpAddress.get());
            return false;
        }
        AVCodecContext *pOrgCodecCtx{pFormatCtx->streams[videoStream]->codec};
        if (pOrgCodecCtx == nullptr) {
            LOGE("ccc %s initContext pOrgCodecCtx end",rtmpAddress.get());
            return false;
        }
        AVCodec *pCodec{avcodec_find_decoder(pOrgCodecCtx->codec_id)};
        if (pCodec == nullptr) {
            LOGE("ccc %s initContext pCodec end",rtmpAddress.get());
            return false;
        }
        pCodecCtx = avcodec_alloc_context3(pCodec);
        if (avcodec_copy_context(pCodecCtx, pOrgCodecCtx) != 0) {
            LOGE("ccc %s initContext avcodec_copy_context end",rtmpAddress.get());
            return false;
        }
        if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0) {
            LOGE("ccc %s initContext avcodec_open2 end",rtmpAddress.get());
            return false;
        }
        if (av_read_pause(pFormatCtx) < 0) {
            LOGE("ccc %s initContext av_read_pause end",rtmpAddress.get());
            return false;
        }
        loop.pause();

        pool.commit(loop([this] {
            renderer();
        }));

        pool.commit(loop([this] {
            decode();
        }));

        pool.commit(loop([this] {
            read();
        }));
        LOGD("ccc %s initContext end",rtmpAddress.get());
        return true;
    }

private:
    int vHeight{0};
    int vWidth{0};
    int videoStream{-1};
    std::shared_ptr<char> rtmpAddress;
    ThreadQueue<std::shared_ptr<AVPacket>> qPacket;//读取完毕后的Packet
    ThreadQueue<std::shared_ptr<AVFrame>> qFrameRGBA;//格式转换后的队列
    ANativeWindow *pNativeWindow{nullptr};
    AVFormatContext *pFormatCtx{nullptr};
    AVCodecContext *pCodecCtx{nullptr};
    ANativeWindow_Buffer windowBuffer;
};